/*
 * @Author: Swance
 * @Date: 2023-12-14
 * @Description: 创建白板编辑器对象的工厂函数
 */

import EventEmitter from 'events';
import { fabric } from 'fabric';
import { IContextMenu, IPlugin, IPluginOption } from '@/types/global';
import hotkeys from 'hotkeys-js';
import ContextMenu from '@/core/ContextMenu.ts';
import { BoardSyncEvent, JsonOption } from '@/utils/Const.ts';
import Action from '@/utils/Action.ts';

type IPluginClass = any;
class Editor extends EventEmitter {
  // 画布对象（所有插件共享）
  canvas: fabric.Canvas;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  contextMenu: any;
  // 元素引用与ID映射关系
  elementsMap: Map<String, fabric.Object>;
  // WebSocket 对象
  webSocket: WebSocket | null;
  // 白板加载状态
  canvasloading: boolean = false;
  // 实时保存数据的定时器
  timer: any = null;
  // 插件集合
  private pluginsMap: {
    [propName: string]: IPlugin;
  } = {};
  // 编辑器事件
  private editorEvents: string[] = [];
  // 编辑器API
  private editorApis: string[] = [];
  [propName: string]: any;

  init(canvas: fabric.Canvas) {
    this.canvas = canvas;
    this.initContextMenu();
    this._bindingContextMenu();
  }

  use(plugin: IPluginClass, options?: IPluginOption) {
    if (this.checkPlugin(plugin)) {
      this.saveAttr(plugin);
      const pluginObject = new plugin(this.canvas, this, options);
      this.pluginsMap[plugin.pluginName] = pluginObject;
      this.proxyApis(pluginObject);
      this._bindingHotKeys(pluginObject);
    }
  }

  // 检查安装的插件、插件事件、插件API是否重复
  private checkPlugin(plugin: IPluginClass) {
    const { pluginName, events = [], apis = [] } = plugin;
    if (this.pluginsMap[pluginName]) {
      throw new Error(pluginName + '插件重复初始化');
    }
    events.forEach((eventName: string) => {
      if (this.editorEvents.find((info) => info === eventName)) {
        throw new Error(pluginName + '插件中' + eventName + '重复');
      }
    });
    apis.forEach((apiName: string) => {
      if (this.editorApis.find((info) => info === apiName)) {
        throw new Error(pluginName + '插件中' + apiName + '重复');
      }
    });
    return true;
  }

  // 保存插件事件、API的名称在编辑器中
  private saveAttr(plugin: IPluginClass) {
    const { events = [], apis = [] } = plugin;
    this.editorApis = this.editorApis.concat(apis);
    this.editorEvents = this.editorEvents.concat(events);
  }

  // 代理 API 事件
  private proxyApis(pluginObject: IPlugin) {
    const { apis = [] } = pluginObject.constructor;
    apis.forEach((apiName: string) => {
      this[apiName] = function () {
        return pluginObject[apiName].apply(pluginObject, [...arguments]);
      };
    });
  }

  // 绑定快捷键
  private _bindingHotKeys(plugin: IPlugin) {
    plugin?.hotkeys?.forEach((keyName: string) => {
      hotkeys(keyName, { keyup: true }, (e) => plugin.hotkeyEvent(keyName, e));
    });
  }

  // 初始化右键菜单
  initContextMenu() {
    this.contextMenu = new ContextMenu(this.canvas.wrapperEl, []);
    this.contextMenu.install();
  }

  // 绑定右键菜单
  private _bindingContextMenu() {
    this.canvas.on('mouse:down', (e: fabric.IEvent) => {
      if (e.button === 3) {
        let menu: IContextMenu = [];
        Object.keys(this.pluginsMap).forEach((pluginName) => {
          const pluginObject = this.pluginsMap[pluginName];
          const pluginMenu =
            pluginObject.contextMenu && pluginObject.contextMenu();
          if (pluginMenu) {
            menu = menu.concat(pluginMenu);
          }
        });
        this._renderContextMenu(e, menu);
      }
    });
  }

  // 渲染右键菜单
  private _renderContextMenu(e: fabric.IEvent, menu: IContextMenu) {
    if (menu.length !== 0) {
      this.contextMenu.hide();
      this.contextMenu.setMenuItems(menu);
      this.contextMenu.showContextMenu(e.e.clientX, e.e.clientY);
    }
  }

  // 初始化画布中 所有元素引用 与 对应ID 之间的 映射关系
  initElementsMap() {
    this.elementsMap = new Map<String, fabric.Object>();
    const allElements = this.canvas.getObjects();
    if (allElements.length === 0) return;
    allElements.forEach((item) => {
      this.elementsMap.set(item.id, item);
    });
    console.log('elementsMap', this.elementsMap);
  }

  // 画布添加元素时，elementsMap添加其映射关系
  addElementMap(newElements: fabric.Object[]) {
    if (newElements.length && newElements) {
      newElements.forEach((item) => {
        this.elementsMap.set(item.id, item);
      });
    }
    console.log('elementsMap add', this.elementsMap);
  }

  getElementById(id: String) {
    if (this.elementsMap.has(id)) {
      return this.elementsMap.get(id);
    }
    return undefined;
  }

  // 画布移除元素时，elementsMap移除其映射关系
  removeElementMap(oldElements: fabric.Object[]) {
    if (oldElements && oldElements.length) {
      oldElements.forEach((item) => {
        if (this.elementsMap.has(item.id)) {
          this.elementsMap.delete(item.id);
        }
      });
    }
    console.log('elementsMap remove', this.elementsMap);
  }

  // 计算多选框内各个元素的绝对位置并赋值，输出JSON数组（top & left）
  calcAbsolutePositionFromMultiple(): any[] {
    const activeSelection = this.canvas.getActiveObject();
    if (!activeSelection) return [];
    // 获取 activeSelection 的变换矩阵，通过矩阵计算内里元素的准确位置
    const matrix = activeSelection.calcTransformMatrix();
    const allActiveObjects = activeSelection.getObjects();
    return allActiveObjects.map((item: any) => {
      const objectPosition = { x: item.left, y: item.top };
      const finalPosition = fabric.util.transformPoint(objectPosition, matrix);
      const itemJson = item.toJSON(JsonOption);
      itemJson.top = finalPosition.y;
      itemJson.left = finalPosition.x;
      return itemJson;
    });
  }

  // 建立 WebSocket 连接
  buildWebSocketConnect(boardId: string, token: string) {
    this.webSocket = null;
    // 建立连接前 白板处于加载状态
    this.canvasloading = true;

    const that = this;
    // 创建对应白板的 WebSocket 对象
    this.webSocket = new WebSocket(
      `${import.meta.env.VITE_WEB_SOCKET_URL}/${boardId}?satoken=${token}`,
      // `${import.meta.env.VITE_WEB_SOCKET_URL}/abcd?satoken=${token}`,
    );
    let ws = this.webSocket;
    this.listenCanvasSync();
    // 连接成功后的回调函数
    ws.onopen = function (params) {
      console.log('WebSocket连接成功', params);
    };
    // 从服务端接收到信息时的回调函数
    ws.onmessage = function (e) {
      console.log('WebSocket收到服务器响应', JSON.parse(e.data));
      const response = JSON.parse(e.data);
      switch (response.type) {
        // 收到画布初始化数据
        case 0: {
          if (response?.data.dataJsonStr) {
            that.JsonToCanvas(response?.data.dataJsonStr);
          }
          // 当白板加载完画布初始数据后，白板才停止加载状态
          that.canvasloading = false;
          that.initElementsMap();
          // 等白板加载完后才开始实时保存白板数据的定时器
          that.timer = setInterval(() => {
            console.log('定时器实时保存');
            that.sendMessage({
              type: BoardSyncEvent.BOARD_DATA,
              data: that.canvasToJSON(),
            });
            that.emit(BoardSyncEvent.saveTime);
          }, 10000);
          that.emit(BoardSyncEvent.type0, response.data);
          break;
        }
        // 收到其他用户的新操作
        case 1: {
          that.executeAction(JSON.parse(response.data));
          console.log('收到其他用户的新操作');
          break;
        }
        // 收到白板协作数据
        case 2: {
          that.emit(BoardSyncEvent.type2, response.data);
          break;
        }
        // 服务端 请求 白板数据（发送白板数据给服务端）
        case 3: {
          that.sendMessage({
            type: BoardSyncEvent.BOARD_DATA,
            data: that.canvasToJSON(),
          });
          break;
        }
        // 服务端通知用户哪些元素需要上锁，不可操作
        case 4: {
          that.lockElementsByIDs(response.data);
          break;
        }
        // 服务端通知用户哪些元素需要解锁，可操作
        case 5: {
          that.unlockElementsByIDs(response.data);
          break;
        }
      }
    };
    // 连接关闭后的回调函数
    ws.onclose = function (e) {
      console.log('WebSocket连接断开', e);
      that.emit(BoardSyncEvent.Disconnected, e);
    };
    // 连接失败后的回调函数
    ws.onerror = function (e) {
      console.error('WebSocket连接失败', e);
    };
    // 监听窗口关闭事件，当窗口关闭时，主动去关闭websocket连接，防止连接还没断开就关闭窗口，这样服务端会抛异常。
    window.onbeforeunload = function () {
      // 主动关闭 WebSocket 连接
      that.disconnectWebSocket();
    };
  }

  // 发送 WebSocket 信息
  sendMessage(data: { data: string; type: BoardSyncEvent }) {
    if (!this.webSocket) return;
    this.webSocket.send(JSON.stringify(data));
  }

  // 关闭 WebSocket 连接
  disconnectWebSocket() {
    if (!this.webSocket) return;
    // TODO: 场景：用户初次进入白板，在获得初始白板数据前退出白板，此时退出白板不发送白板封面和白板数据
    // 实现方案：画布的初始数据只会接收一次（即每一次建立WebSocket连接）
    // 当白板处于加载状态下，离开白板编辑器不发送白板封面和白板数据
    if (!this.canvasloading) {
      // 关闭WebSocket连接前，发送 白板封面 给服务端
      this.sendMessage({
        type: BoardSyncEvent.COVER,
        data: this.createBoardCover(),
      });
      // 关闭WebSocket连接前，发送 白板数据 给服务端
      this.sendMessage({
        type: BoardSyncEvent.BOARD_DATA,
        data: this.canvasToJSON(),
      });
    }
    this.webSocket.close();
    clearInterval(this.timer);
    this.timer = null;
    this.webSocket = null;
  }

  // 序列化整个画布
  canvasToJSON() {
    return JSON.stringify(this.canvas.toJSON(JsonOption));
  }

  // 反序列化画布
  JsonToCanvas(data: JSON | string) {
    this.canvas.loadFromJSON(data, this.canvas.renderAll.bind(this.canvas));
  }

  // 生成白板封面
  createBoardCover() {
    let dataUrl = '';
    // canvas 转为 jpeg 之前会移除 alpha 通道，所以透明区域被填充成了黑色，故输出前把 canvas 的背景色改为纯白再输出
    this.canvas.setBackgroundColor('rgb(255, 255, 255)', () => {
      dataUrl = this.canvas.toDataURL({
        width: this.canvas.width,
        height: this.canvas.height,
        left: 0,
        top: 0,
        format: 'jpeg',
      });
      this.canvas.setBackgroundColor('', () => {});
    });
    return dataUrl;
  }

  // 监听需要同步至服务端的白板同步事件
  listenCanvasSync() {
    this.on(BoardSyncEvent.Sync, (data) => {
      console.log('画布同步事件', data);
      this.sendMessage({
        type: BoardSyncEvent.NEW_ACTION,
        data: JSON.stringify(data),
      });
    });
  }

  // 执行通信过程中接收到的 action
  executeAction(action: Action) {
    if (!action) return;

    switch (action.type) {
      // 执行 删除 操作
      case BoardSyncEvent.REMOVE: {
        if (!action.oldElements.length) return;
        const oldElementsObjects = action.oldElements.map((item: any) => {
          return this.getElementById(item.id);
        });
        this.canvas.remove(...oldElementsObjects);
        this.removeElementMap(oldElementsObjects);
        break;
      }
      // 执行 添加 操作
      case BoardSyncEvent.ADD: {
        const that = this;
        if (!action.newElements.length) return;
        fabric.util.enlivenObjects(
          action.newElements,
          function (objects: any[]) {
            that.canvas.add(...objects);
            that.addElementMap(objects);
          },
          'fabric',
        );
        break;
      }
      // 执行 修改 操作
      case BoardSyncEvent.UPDATE: {
        if (!action.oldElements.length && !action.newElements.length) return;
        // 删除旧状态
        const oldElementsObjects = action.oldElements.map((item: any) => {
          return this.getElementById(item.id);
        });
        this.canvas.remove(...oldElementsObjects);
        this.removeElementMap(oldElementsObjects);
        // 添加新状态
        const that = this;
        fabric.util.enlivenObjects(
          action.newElements,
          function (objects: any[]) {
            that.canvas.add(...objects);
            that.addElementMap(objects);
          },
          'fabric',
        );
        break;
      }
      // 执行 置于顶层 操作
      case BoardSyncEvent.UP: {
        const that = this;
        if (!action.oldElements.length) return;
        const oldElementsObjects = action.oldElements.map((item: any) => {
          return this.getElementById(item.id);
        });
        this.canvas.remove(...oldElementsObjects);
        // this.editor.removeElementMap(oldElementsObjects);
        fabric.util.enlivenObjects(
          action.oldElements,
          function (objects: any[]) {
            that.canvas.add(...objects);
            objects.forEach((item) => {
              that.canvas.bringToFront(item);
            });
            that.addElementMap(objects);
          },
          'fabric',
        );
        break;
      }
      case BoardSyncEvent.DOWN: {
        const that = this;
        if (!action.oldElements.length) return;
        const oldElementsObjects = action.oldElements.map((item: any) => {
          return this.getElementById(item.id);
        });
        this.canvas.remove(...oldElementsObjects);
        // this.editor.removeElementMap(oldElementsObjects);
        fabric.util.enlivenObjects(
          action.oldElements,
          function (objects: any[]) {
            that.canvas.add(...objects);
            objects.forEach((item) => {
              that.canvas.sendToBack(item);
            });
            that.addElementMap(objects);
          },
          'fabric',
        );
        break;
      }
    }
  }

  // 锁定元素，用于解决白板同步中的冲突
  lockElementOnCanvas(element: fabric.Object) {
    element.set('selectable', false);
    element.set('evented', false);
    element.set('lockMovementX', true);
    element.set('lockMovementY', true);
    element.set('hasControls', false);
    element.set('dirty', true);
    element.lock = true;
    if (element.type === 'textbox') {
      element.set('editable', false);
    } else if (element.type === 'path') {
      element.set('erasable', false);
    }
  }
  // 对服务器发来的ID列表为对应元素上锁
  lockElementsByIDs(data: string) {
    const ids = JSON.parse(data);
    if (typeof ids === 'string') {
      const element = this.getElementById(ids);
      if (!element) return;
      this.lockElementOnCanvas(element);
    } else if (Array.isArray(ids)) {
      ids.forEach((id: string) => {
        const element = this.getElementById(id);
        if (element) {
          this.lockElementOnCanvas(element);
        }
      });
    }
  }

  // 解锁元素，用于解决白板同步发生的冲突
  unlockElementOnCanvas(element: fabric.Object) {
    element.set('selectable', true);
    element.set('evented', true);
    element.set('lockMovementX', false);
    element.set('lockMovementY', false);
    element.set('hasControls', true);
    element.set('dirty', true);
    element.lock = false;
    if (element.type === 'textbox') {
      element.set('editable', true);
    } else if (element.type === 'path') {
      element.set('erasable', true);
    }
  }
  // 对服务器发来的ID列表为对应元素解锁
  unlockElementsByIDs(data: string) {
    const ids = JSON.parse(data);
    if (typeof ids === 'string') {
      const element = this.getElementById(ids);
      if (!element) return;
      this.unlockElementOnCanvas(element);
    } else if (Array.isArray(ids)) {
      ids.forEach((id: string) => {
        const element = this.getElementById(id);
        if (element) {
          this.unlockElementOnCanvas(element);
        }
      });
    }
  }
}

export default Editor;
